//
// Created by yanhai on 2020/2/20.
//

#pragma once

#include <iostream>
#include <map>
#include <string>
#include <vector>

#include "http.hpp"
#include "datastructures.hpp"

typedef MultiDict<std::string, std::string> form_type;
typedef MultiDict<std::string, FileStorage> file_type;

class MultiPartParser {
public:
    MultiPartParser() = default;

    std::tuple<form_type, file_type> parse(std::string &stream, std::string _boundary, int content_length);
};

std::tuple<form_type, file_type> MultiPartParser::parse(std::string &stream,
                                                        std::string _boundary,
                                                        int content_length)
{
    form_type form;
    file_type file;
    std::string boundary = "--" + _boundary;
    std::string boundary_end = boundary + "--";
    if (!boost::ends_with(stream, boundary_end + "\r\n")) {
        throw RequestParseBodyException("not found end boundary mark");
    }
    auto __form = utility::string::split(stream, boundary,
                                         [](const std::string &s) -> std::string {
                                             return boost::trim_copy_if(s, boost::is_any_of(" \t\r\n"));
                                         });
    for (auto &_form: __form) {
        if (boost::starts_with(_form, "Content-Disposition:")) {
            auto key_value = utility::string::split(_form, "\r\n\r\n",
                                                    [](const std::string &s) -> std::string {
                                                        return boost::trim_copy_if(s, boost::is_any_of(" \t\r\n"));
                                                    });
            if (key_value.size() != 2) {
                throw RequestParseBodyException("MultiPartParser parse exception: " + _form);
            }
            auto Content_Disposition_value = key_value[0].substr(std::string("Content-Disposition:").size());
            if (Content_Disposition_value.find("\r\n") != std::string::npos) {
                Content_Disposition_value = Content_Disposition_value.substr(0, Content_Disposition_value.find("\r\n"));
            }

            auto options = boost::trim_copy_if(Content_Disposition_value, boost::is_any_of(" \t\r\n"));
            auto r = parse_options_header(options);
            auto &mimetype = std::get<0>(r);
            auto &options1 = std::get<1>(r);

            if (options1.find("filename") != options1.end()) {
                file.insert(options1["name"], FileStorage(key_value[1], options1["filename"]));
            } else {
                form.insert(options1["name"], key_value[1]);
            }
        }
    }
    return std::make_tuple(form, file);
}

class FormDataParser {
    typedef std::function<
            std::tuple<form_type, file_type>(std::string &,
                                             std::string &,
                                             int,
                                             const options_type &)>
            _parse_function_type;

public:
    FormDataParser();

    // return tuple<form, files>
    std::tuple<form_type, file_type> parse(std::string &stream,
                                           std::string &mimetype,
                                           int content_length,
                                           const options_type &options);

private:
    std::tuple<form_type, file_type> _parse_multipart(std::string &stream,
                                                      std::string &mimetype,
                                                      int content_length,
                                                      const options_type &options);

    std::tuple<form_type, file_type> _parse_urlencoded(std::string &stream,
                                                       std::string &mimetype,
                                                       int content_length,
                                                       const options_type &options);

    _parse_function_type get_parse_func(std::string &mimetype);

private:
    std::map<std::string, _parse_function_type> parse_functions;
};

FormDataParser::FormDataParser()
{
    parse_functions["multipart/form-data"] =
            std::bind(&FormDataParser::_parse_multipart, this,
                      std::placeholders::_1, std::placeholders::_2,
                      std::placeholders::_3, std::placeholders::_4);
    parse_functions["application/x-www-form-urlencoded"] =
            std::bind(&FormDataParser::_parse_urlencoded, this,
                      std::placeholders::_1, std::placeholders::_2,
                      std::placeholders::_3, std::placeholders::_4);
    parse_functions["application/x-url-encoded"] =
            std::bind(&FormDataParser::_parse_urlencoded, this,
                      std::placeholders::_1, std::placeholders::_2,
                      std::placeholders::_3, std::placeholders::_4);
}

/**
 * Parses the information from the given stream, mimetype,
 * content length and mimetype parameters.
 * @param stream: an input stream
 * @param mimetype: the mimetype of the data
 * @param content_length: the content length of the incoming data
 * @param options: optional mimetype parameters (used for
 *                 the multipart boundary for instance)
 * @return A tuple in the form ``(form, files)``.
 */
std::tuple<form_type, file_type>
FormDataParser::parse(std::string &stream, std::string &mimetype, int content_length,
                      const options_type &options)
{
    FormDataParser::_parse_function_type parse_func;

    if (stream.empty()) {
        goto end;
    }

    parse_func = get_parse_func(mimetype);
    if (parse_func != nullptr) {
        try {
            return parse_func(stream, mimetype, content_length, options);
        } catch (...) {
            throw;
        }
    }

end:
    return std::make_tuple(form_type(), file_type());
}

FormDataParser::_parse_function_type
FormDataParser::get_parse_func(std::string &mimetype)
{
    auto it = parse_functions.find(mimetype);
    if (it != parse_functions.end()) {
        return it->second;
    } else {
        return nullptr;
    }
}

std::tuple<form_type, file_type>
FormDataParser::_parse_multipart(std::string &stream,
                                 std::string &mimetype,
                                 int content_length,
                                 const options_type &options)
{
    MultiPartParser parser;
    auto boundary = options.find("boundary");
    if (boundary == options.end()) {
        // todo throw ValueError("Missing boundary");
        return std::make_tuple(form_type(), file_type());
    }
    return parser.parse(stream, boundary->second, content_length);
}

std::tuple<form_type, file_type>
FormDataParser::_parse_urlencoded(std::string &stream,
                                  std::string &mimetype,
                                  int content_length,
                                  const options_type &options)
{
    form_type form;
    std::vector<std::string> v;
    // 三种方式都可以
//    boost::split(v, s, std::bind2nd(std::equal_to<char>(), '&'));
//    boost::split(v, s, boost::is_any_of("&"));
//    boost::split(v, s, [](const char c) -> bool { return c == '&'; });
    boost::split(v, stream, [](const char c) -> bool { return c == '&'; });
    for (auto &item: v) {
        std::vector<std::string> p;
        boost::split(p, item, [](const char c) -> bool { return c == '='; });
        if (p.size() != 2) {
            throw RequestParseBodyException("urlencoded parse exception: " + stream);
        }
        form.insert(utility::URLDecode(p[0]), utility::URLDecode(p[1]));
    }
    return std::make_tuple(form, file_type());
}
